#include "MethodDefinition.h"
#include "../Exceptions.h"
#include "../GeneralVisitor.h"
#include "../Constants.h"

MethodDefinition::MethodDefinition(string iMethodName, vector<Variable*> iInputVariables, TreeNode *iBody)
{
    this->fMethodName = iMethodName;
    this->fInputVariables = iInputVariables;
    this->fBody = iBody;
    this->fClassDef = 0;
    this->fBytecodeOffset = 0;
    this->fIsConstructor = false;
}

MethodDefinition::~MethodDefinition()
{
    delete this->fBody;

    for(vector<Variable*>::iterator it = this->fInputVariables.begin(); it != fInputVariables.end(); ++it)
    {
        delete (*it);
    }

    this->fInputVariables.clear();
}

void MethodDefinition::CheckFunctionCalls(set<FunctionDefinition*>& iAllFunctions, string iParentName)
{
    this->fBody->CheckFunctionCalls(iAllFunctions, iParentName);
}

void MethodDefinition::CheckClassConstructions(set<ClassDefinition*>& iAllClasses, string iParentName)
{
    this->fBody->CheckClassConstructions(iAllClasses, iParentName);
}

void MethodDefinition::CheckVariables(set<string>& iClassFieldNames, string iParentName)
{
    fLocalVars.insert("this");
    fLocalVars.insert(iClassFieldNames.begin(), iClassFieldNames.end());

    // nejdriv zkontrolovat vstupni parametry, jestli nejake nemaji stejne jmeno...
    // a nebo stejne jmeno jako nejaky field tridy...
    // a nebo this...
    for(vector<Variable*>::iterator it = this->fInputVariables.begin(); it != fInputVariables.end(); ++it)
    {
        string lVarName = (*it)->GetVarName();

        if(fLocalVars.find(lVarName) != fLocalVars.end())
        {
            throw DuplicateParameterNameException(iParentName + "::" + fMethodName, lVarName);
        }

        fLocalVars.insert(lVarName);
    }

    fBody->CheckVariables(fLocalVars, iParentName + "::" + fMethodName);

    // a tady z toho opet odeber this a jmena fieldu - to jsem do setu vlozil proto, aby spravne fungovala kontrola na pouziti
    // promennych
    fLocalVars.erase("this");

    for(set<string>::iterator it = iClassFieldNames.begin(); it != iClassFieldNames.end(); ++it)
    {
        fLocalVars.erase(*it);
    }

    if(fLocalVars.size() + 1 > MAX_METHOD_LOCAL_VARS) // +1: to je za this
        throw TooMuchLocalVars(iParentName + "::" + fMethodName, MAX_METHOD_LOCAL_VARS, fLocalVars.size() + 1);
}

ReturnStatement* MethodDefinition::GetReturnStatement()
{
    return fBody->GetFirstReturnStatement();
}

void MethodDefinition::CheckExpressionTypes(set<string>& iClassFieldNames, string iParentName)
{
    map<string, ExpressionType*> lVarTypes;

    // pridej tam lokalni promenne a vstupni parametry...
    for(set<string>::iterator it = fLocalVars.begin(); it != fLocalVars.end(); ++it)
    {
        lVarTypes[*it] = new ExpressionType(Unresolved);
    }

    // ... primichej k tomu fieldy...
    for(set<string>::iterator it = iClassFieldNames.begin(); it != iClassFieldNames.end(); ++it)
    {
        lVarTypes[*it] = new ExpressionType(Unresolved);
    }

    // ... a okoren to this
    lVarTypes["this"] = new ExpressionType(UserDefinedObject, false, this->fClassDef);

    try
    {
        fBody->CheckExpressionTypes(lVarTypes, iParentName + "::" + fMethodName);
    }
    catch(BaseException&)
    {

        for(map<string, ExpressionType*>::iterator it = lVarTypes.begin(); it != lVarTypes.end(); ++it)
        {
            delete (*it).second;
        }

        throw;
    }

    for(map<string, ExpressionType*>::iterator it = lVarTypes.begin(); it != lVarTypes.end(); ++it)
    {
        delete (*it).second;
    }

    lVarTypes.clear();
}

void MethodDefinition::Accept(GeneralVisitor* iVisitor)
{
    iVisitor->Visit(this);
}

vector<string> MethodDefinition::GetInputVarNames()
{
    vector<string> lResult;

    for(vector<Variable*>::iterator it = fInputVariables.begin(); it != fInputVariables.end(); ++it)
    {
        lResult.push_back((*it)->GetVarName());
    }

    return lResult;
}

vector<string> MethodDefinition::GetLocalVarNames()
{
    vector<string> lResult;

    // Problem je, ze v fLocalVars jsou zahrnuty i vstupni parametry - musime to osetrit.
    for(set<string>::iterator it = fLocalVars.begin(); it != fLocalVars.end(); ++it)
    {
        bool lIsParam = false;

        for(vector<Variable*>::iterator it2 = fInputVariables.begin(); it2 != fInputVariables.end(); ++it2)
        {
            if((*it) == (*it2)->GetVarName())
            {
                lIsParam = true;
                break;
            }
        }

        if(!lIsParam)
            lResult.push_back(*it);
    }

    return lResult;
}
